//***************************************************************************

//

//  WBEMSEC.CPP

//

//  Purpose: Provides some security helper functions.

//

// Copyright (c)  Microsoft Corporation, All Rights Reserved
//
//***************************************************************************

//#undef _WIN32_WINNT
//#define _WIN32_WINNT 0x0400
#include "precomp.h"
#include <wbemidl.h>
#include "wbemsec.h"

//***************************************************************************
//
//  InitializeSecurity(DWORD dwAuthLevel, DWORD dwImpLevel)
//
//  DESCRIPTION:
//
//  Initialize DCOM security.  The authentication level is typically
//  RPC_C_AUTHN_LEVEL_PKT_PRIVACY,  and the impersonation level is typically
//  RPC_C_IMP_LEVEL_IMPERSONATE.  
//  When using asynchronous WMI callbacks remotely in an environment where the "Local System" account 
//  has no network identity (such as non-Kerberos domains), the authentication level of 
//  RPC_C_AUTHN_LEVEL_NONE is needed. However, lowering the authentication level to 
//  RPC_C_AUTHN_LEVEL_NONE makes your application less secure. It is wise to
//	use semi-synchronous API's for accessing WMI data and events instead of the asynchronous ones.
//
//  RETURN VALUE:
//
//  see description.
//
//***************************************************************************

HRESULT InitializeSecurity(DWORD dwAuthLevel, DWORD dwImpLevel)
{
    // Initialize security
    // ===================

	DWORD dwCapabilities;

	if (dwAuthLevel == RPC_C_AUTHN_LEVEL_NONE) 
        dwCapabilities = EOAC_NONE;
	else
        dwCapabilities = EOAC_SECURE_REFS;


    return CoInitializeSecurity
		(NULL, -1, NULL, NULL,
        dwAuthLevel, dwImpLevel,
        NULL, 
		dwCapabilities, 
		0);
}

//***************************************************************************
//
//  bool bIsNT
//
//  DESCRIPTION:
//
//  Returns true if running windows NT.
//
//  RETURN VALUE:
//
//  see description.
//
//***************************************************************************

bool bIsNT(void)
{
    OSVERSIONINFO os;
    os.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    if(!GetVersionEx(&os))
        return FALSE;           // should never happen
    return os.dwPlatformId == VER_PLATFORM_WIN32_NT;
}


//***************************************************************************
//
//  SCODE ParseAuthorityUserArgs
//
//  DESCRIPTION:
//
//	NOTE: For Windows XP or later, consider calling CredUIParseUserName() instead of this method.
//
//  Examines the Authority and User argument and determines the authentication
//  type and possibly extracts the domain name from the user arugment in the
//  NTLM case.  For NTLM, the domain can be at the end of the authentication
//  string, or in the front of the user name, ex:  "TestDomain\JoeUser"
//
//  PARAMETERS:
//
//  ConnType            Returned with the connection type, ie wbem, ntlm
//  AuthArg             Output, contains the domain name
//  UserArg             Output, user name
//  Authority           Input
//  User                Input
//
//  RETURN VALUE:
//
//  S_OK                all is well
//  else error listed in WBEMSVC.H
//
//***************************************************************************


SCODE ParseAuthorityUserArgs(BSTR & AuthArg, BSTR & UserArg,BSTR & Authority,BSTR & User)
{

    // Determine the connection type by examining the Authority string

    if(!(Authority == NULL || wcslen(Authority) == 0 || !_wcsnicmp(Authority, L"NTLMDOMAIN:",11)))
        return E_INVALIDARG;

    // The ntlm case is more complex.  There are four cases
    // 1)  Authority = NTLMDOMAIN:name" and User = "User"
    // 2)  Authority = NULL and User = "User"
    // 3)  Authority = "NTLMDOMAIN:" User = "domain\user"
    // 4)  Authority = NULL and User = "domain\user"

    // first step is to determine if there is a backslash in the user name somewhere between the
    // second and second to last character

    WCHAR * pSlashInUser = NULL;
    if(User)
    {
        WCHAR * pEnd = User + wcslen(User) - 1;
        for(pSlashInUser = User; pSlashInUser <= pEnd; pSlashInUser++)
            if(*pSlashInUser == L'\\')      // dont think forward slash is allowed!
                break;
        if(pSlashInUser > pEnd)
            pSlashInUser = NULL;
    }

    if(Authority && wcslen(Authority) > 11)
    {
        if(pSlashInUser)
            return E_INVALIDARG;

        AuthArg = SysAllocString(Authority + 11);
		if (!AuthArg)
			return E_OUTOFMEMORY;
        if(User) 
		{
			UserArg = SysAllocString(User);
			if (!UserArg)
				return E_OUTOFMEMORY;
		}

        return S_OK;
    }
    else if(pSlashInUser)
    {
        INT_PTR iDomLen = min(MAX_PATH-1, pSlashInUser-User);
        WCHAR cTemp[MAX_PATH];
        wcsncpy_s(cTemp, User, iDomLen);
        cTemp[iDomLen] = 0;
        AuthArg = SysAllocString(cTemp);
		if (!AuthArg)
			return E_OUTOFMEMORY;
        if(wcslen(pSlashInUser+1))
		{
            UserArg = SysAllocString(pSlashInUser+1);
			if (!UserArg)
				return E_OUTOFMEMORY;
		}

    }
    else
        if(User) 
		{
			UserArg = SysAllocString(User);
			if (!UserArg)
				return E_OUTOFMEMORY;
		}

    return S_OK;
}


//***************************************************************************
//
//  SCODE GetAuthImp
//
//  DESCRIPTION:
//
//  Gets the authentication and impersonation levels for a current interface.
//
//  PARAMETERS:
//
//  pFrom               the interface to be tested.
//  pdwAuthLevel    Set to the authentication level
//  pdwImpLevel    Set to the impersonation level
//  RETURN VALUE:
//
//  S_OK                all is well
//  else error listed in WBEMSVC.H
//
//***************************************************************************

SCODE GetAuthImp(IUnknown * pFrom, DWORD * pdwAuthLevel, DWORD * pdwImpLevel)
{

    if(pFrom == NULL || pdwAuthLevel == NULL || pdwImpLevel == NULL)
        return WBEM_E_INVALID_PARAMETER;

    IClientSecurity * pFromSec = NULL;
    SCODE sc = pFrom->QueryInterface(IID_IClientSecurity, (void **) &pFromSec);
    if(sc == S_OK)
    {
        DWORD dwAuthnSvc, dwAuthzSvc, dwCapabilities;
        sc = pFromSec->QueryBlanket(pFrom, &dwAuthnSvc, &dwAuthzSvc,
                                            NULL,
                                            pdwAuthLevel, pdwImpLevel,
                                            NULL, &dwCapabilities);

        // Special case of going to a win9x share level box

        if (sc == 0x800706d2)
        {
            *pdwAuthLevel = RPC_C_AUTHN_LEVEL_NONE;
            *pdwImpLevel = RPC_C_IMP_LEVEL_IDENTIFY;
            sc = S_OK;
        }
        pFromSec->Release();
    }
    return sc;
}
//***************************************************************************
//
//  SCODE SetInterfaceSecurity
//
//  DESCRIPTION:
//
//  This routine is used by clients in order to set the security settings of a particular 
//  interface proxy. NOTE that setting the security blanket on the interface is not always needed:
//  simple client applications that execute in their own process can typically just call 
//	CoInitializeSecurity( NULL, -1, NULL, NULL, 
//						RPC_C_AUTHN_LEVEL_PKT_PRIVACY, 
//						RPC_C_IMP_LEVEL_IMPERSONATE, 
//						NULL, 
//						EOAC_NONE, 
//						NULL );
//  before calling out to WMI.  
//  However, for clients that reside in DLLs and do not control 
//  the hosting process' COM security setting (MMC snap-ins, Visual Studio extensions and such), 
//  it is necessary to set security on WMI interface proxies by calling 
//  CoSetProxyBlanket() directly or through this helper.  
//
//	Another case where setting proxy security for WMI connection is needed is when 
//  credentials different from the client process user context must be specified.
//
//
//  PARAMETERS:
//
//  pInterface       Interface to be set
//  pDomain          Input, domain
//  pUser            Input, user name
//  pPassword        Input, password.
//  pFrom            Input, if not NULL, then the authentication level of this interface
//                      is used
//  bAuthArg          If pFrom is NULL, then this is the authentication level
//  RETURN VALUE:
//
//  S_OK                all is well
//  else error listed in WBEMSVC.H
//
//***************************************************************************

HRESULT SetInterfaceSecurity(IUnknown * pInterface, LPWSTR pAuthority, LPWSTR pUser,
                             LPWSTR pPassword, DWORD dwAuthLevel, DWORD dwImpLevel)
{

    SCODE sc;
    if(pInterface == NULL)
        return E_INVALIDARG;

    // If we are lowering the security, no need to deal with the identification info

    if(dwAuthLevel == RPC_C_AUTHN_LEVEL_NONE)
        return CoSetProxyBlanket(pInterface, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
                       RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);

    // If we are doing trivial case, just pass in a null authentication structure which is used
    // if the current logged in user's credentials are OK.

    if((pAuthority == NULL || wcslen(pAuthority) < 1) &&
        (pUser == NULL || wcslen(pUser) < 1) &&
        (pPassword == NULL || wcslen(pPassword) < 1))
            return CoSetProxyBlanket(pInterface, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
                       dwAuthLevel, dwImpLevel, NULL, EOAC_NONE);

    // If user, or Authority was passed in, the we need to create an authority argument for the login

    COAUTHIDENTITY  authident;
    BSTR AuthArg = NULL, UserArg = NULL;
    sc = ParseAuthorityUserArgs(AuthArg, UserArg, pAuthority, pUser);
	size_t * intReturnValue = NULL;
    if(sc != S_OK)
        return sc;

    memset((void *)&authident,0,sizeof(COAUTHIDENTITY));
    if(bIsNT())
    {
        if(UserArg)
        {
            authident.UserLength = (ULONG)wcslen(UserArg);
            authident.User = (USHORT*)UserArg;
        }
        if(AuthArg)
        {
            authident.DomainLength = (ULONG)wcslen(AuthArg);
            authident.Domain = (USHORT*)AuthArg;
        }
        if(pPassword)
        {
            authident.PasswordLength = (ULONG)wcslen(pPassword);
            authident.Password = (USHORT*)pPassword;
        }
        authident.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
    }
    else
    {
        char szUser[MAX_PATH], szAuthority[MAX_PATH], szPassword[MAX_PATH];
		ZeroMemory(szUser, sizeof(szUser));
		ZeroMemory(szAuthority, sizeof(szAuthority));
		ZeroMemory(szPassword, sizeof(szPassword));

		size_t written = 0;

        // Fill in the indentity structure

        if(UserArg)
        {
			written = wcstombs_s(intReturnValue, szUser, sizeof(szUser), UserArg, sizeof(szUser));
            //written = wcstombs(szUser, UserArg, sizeof(szUser));
			if (written < sizeof(szUser) - 1)
			{
				//we could retry by calling wcstombs to find out required buffer size, but this is a simple sample
				sc = E_INVALIDARG;
				goto cleanup;
			}
	
            authident.UserLength = (ULONG)strlen(szUser);
            authident.User = (USHORT*)szUser;
        }
        if(AuthArg)
        {
            written = wcstombs_s(intReturnValue, szAuthority, sizeof(szAuthority), AuthArg, sizeof(szAuthority));
			//written = wcstombs(szAuthority, AuthArg, sizeof(szAuthority));
			if (written < sizeof(szAuthority) - 1)
			{
				//we could retry by calling wcstombs to find out required buffer size, but this is a simple sample
				sc = E_INVALIDARG;
				goto cleanup;
			}
            authident.DomainLength = (ULONG)strlen(szAuthority);
            authident.Domain = (USHORT*)szAuthority;
        }
        if(pPassword)
        {
            written = wcstombs_s(intReturnValue, szPassword, sizeof(szPassword), pPassword, sizeof(szPassword));
			//written = wcstombs(szPassword, pPassword, sizeof(szPassword));
			if (written < sizeof(szPassword) - 1)
			{
				//we could retry by calling wcstombs to find out required buffer size, but this is a simple sample
				sc = E_INVALIDARG;
				goto cleanup;
			}
            authident.PasswordLength = (ULONG)strlen(szPassword);
            authident.Password = (USHORT*)szPassword;
        }
        authident.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
    }
    sc = CoSetProxyBlanket(pInterface, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
                       dwAuthLevel, dwImpLevel, &authident, EOAC_NONE);

cleanup:
    if(UserArg)
        SysFreeString(UserArg);
    if(AuthArg)
        SysFreeString(AuthArg);
    return sc;
}

/******************************************************************************
 *
 *	Name:	CheckAccess
 *				
 *  Description:	Allow provider to evaluate permissions against a security descriptor
 *
 *  This method should be called by WMI providers in scenarios where
 *			they cannot or should not impersonate the client. This happens in two scenarios:
 *			a) when the providers access resources that are not protected by ACL's
 *			b) when the client connects at the impersonation level of RPC_C_IMP_LEVEL_IDENTIFY
 *
 *****************************************************************************/

HRESULT CheckAccess (SECURITY_DESCRIPTOR *a_SecurityDescriptor ,
					DWORD a_Access , 
					GENERIC_MAPPING *a_Mapping)
{
	HRESULT t_Result = S_OK ;

	HANDLE t_Token = NULL ;

	BOOL t_Status = OpenThreadToken (

		GetCurrentThread () ,
		TOKEN_QUERY ,
		TRUE ,
		& t_Token 										
	) ;

	DWORD t_LastError = GetLastError () ;
	if ( ! t_Status)
	{
		//the thread token should always be available

		switch ( t_LastError )
		{
			case E_ACCESSDENIED:
			{
				return 	WBEM_E_ACCESS_DENIED ;
			}
			break ;

			default:
			{
				return WBEM_E_FAILED ;
			}
			break ;
		}
	}
	

	DWORD t_Access = 0 ;
	BOOL t_AccessStatus = FALSE ;
	PRIVILEGE_SET *t_PrivilegeSet = NULL ;
	DWORD t_PrivilegeSetSize = 0 ;

	MapGenericMask (

		& a_Access ,
		a_Mapping
	) ;

	t_Status = AccessCheck (

		a_SecurityDescriptor ,
		t_Token,
		a_Access ,
		a_Mapping ,
		NULL ,
		& t_PrivilegeSetSize ,
		& t_Access ,
		& t_AccessStatus
	) ;

	if (!t_Status || !t_AccessStatus )
	{
		DWORD t_LastError = GetLastError () ;
		if ( t_LastError == ERROR_INSUFFICIENT_BUFFER )
		{
			t_PrivilegeSet = ( PRIVILEGE_SET * ) new BYTE [ t_PrivilegeSetSize ] ;
			if ( t_PrivilegeSet )
			{				
				t_Status = AccessCheck (
					a_SecurityDescriptor ,
					t_Token,
					a_Access ,
					a_Mapping ,
					t_PrivilegeSet ,
					& t_PrivilegeSetSize ,
					& t_Access ,
					& t_AccessStatus
				) ;

				if ( !t_Status || !t_AccessStatus )
				{
					t_Result = WBEM_E_ACCESS_DENIED ;
				}

				delete [] ( BYTE * ) t_PrivilegeSet ;
			}
			else
			{
				t_Result = WBEM_E_OUT_OF_MEMORY ;
			}
		}
		else
		{
			t_Result = WBEM_E_ACCESS_DENIED;
		}

	}

	CloseHandle ( t_Token ) ;	


	return t_Result ;
}



/******************************************************************************
 *
 *	Name:	GetCurrentImpersonationLevel
 *
 *	
 *  Description:
 *
 *			Get COM impersonation level of caller.	
 *
 *****************************************************************************/

DWORD GetCurrentImpersonationLevel ()
{
	DWORD t_ImpersonationLevel = RPC_C_IMP_LEVEL_ANONYMOUS ;

    HANDLE t_ThreadToken = NULL ;

    BOOL t_Status = OpenThreadToken (

		GetCurrentThread() ,
		TOKEN_QUERY,
		TRUE,
		&t_ThreadToken
	) ;

    if ( t_Status )
    {
		SECURITY_IMPERSONATION_LEVEL t_Level = SecurityAnonymous ;
		DWORD t_Returned = 0 ;

		t_Status = GetTokenInformation (

			t_ThreadToken ,
			TokenImpersonationLevel ,
			& t_Level ,
			sizeof ( SECURITY_IMPERSONATION_LEVEL ) ,
			& t_Returned
		) ;

		CloseHandle ( t_ThreadToken ) ;

		if ( t_Status == FALSE )
		{
			t_ImpersonationLevel = RPC_C_IMP_LEVEL_ANONYMOUS ;
		}
		else
		{
			switch ( t_Level )
			{
				case SecurityAnonymous:
				{
					t_ImpersonationLevel = RPC_C_IMP_LEVEL_ANONYMOUS ;
				}
				break ;

				case SecurityIdentification:
				{
					t_ImpersonationLevel = RPC_C_IMP_LEVEL_IDENTIFY ;
				}
				break ;

				case SecurityImpersonation:
				{
					t_ImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE ;
				}
				break ;

				case SecurityDelegation:
				{
					t_ImpersonationLevel = RPC_C_IMP_LEVEL_DELEGATE ;
				}
				break ;

				default:
				{
					t_ImpersonationLevel = RPC_C_IMP_LEVEL_ANONYMOUS ;
				}
				break ;
			}
		}
	}
	else
	{
        ULONG t_LastError = GetLastError () ;

        if ( t_LastError == ERROR_NO_IMPERSONATION_TOKEN || t_LastError == ERROR_NO_TOKEN )
        {
            t_ImpersonationLevel = RPC_C_IMP_LEVEL_DELEGATE ;
        }
        else 
		{
			if ( t_LastError == ERROR_CANT_OPEN_ANONYMOUS )
			{
				t_ImpersonationLevel = RPC_C_IMP_LEVEL_ANONYMOUS ;
			}
			else
			{
				t_ImpersonationLevel = RPC_C_IMP_LEVEL_ANONYMOUS ;
			}
		}
    }

	return t_ImpersonationLevel ;
}


